#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sikle.h"

void type_check(STACEL_TYPE type, STACEL_TYPE type_expected)
{
  if(type != type_expected) {
    printf("ERROR:");
    printf(" type %s expected, but type %s is given", 
	   stacel_types[type_expected].name,
	   stacel_types[type].name);
    exit(0);
  }
}

void register_builtin_word(SikleState* ss, char* name, builtin_word_func bwf)
{
  NString ns = NString_new(name, strlen(name));

  Word word = {WT_BUILTIN, ns, {bwf}};

  Dictionary_put_word(ss->dictionary, word);
}

void _builtin_swp(SikleState* ss)
{
  SContainerStruct cont0;
  SContainerStruct cont1;
  
  StacelType type0;
  StacelType type1;

  type0 = stacel_types[get_stacel_type(ss)];
  type0.operation->pop(ss, STACEL(&cont0));

  type1 = stacel_types[get_stacel_type(ss)];
  type1.operation->pop(ss, STACEL(&cont1));

  type0.operation->push(ss, STACEL(&cont0));
  type1.operation->push(ss, STACEL(&cont1));
}
 
void _builtin_add(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, add, ss);
}

void _builtin_sub(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, sub, ss);
}

void _builtin_mul(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, mul, ss);
}

void _builtin_div(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, div, ss);
}

void _builtin_gt(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, gt, ss);
}

void _builtin_lt(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, lt, ss);
}

void _builtin_ngt(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, ngt, ss);
}

void _builtin_nlt(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, nlt, ss);
}

void _builtin_equ(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, equ, ss);
}

void _builtin_repeat(SikleState* ss)
{
  SAnonymousWord word;
  int condition = 0;

  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  type_check(get_stacel_type(ss), STACEL_ANONYMOUS_WORD);
  type.operation->pop(ss, STACEL(&word));
 
  sp = es->base + es->sp - sizeof(Stacel);
  type = stacel_types[STACEL(sp)->type];

  operation_with_check_cond(type, to_boolean, condition, ss);
  
  while(condition) {
    exec_gramel(ss, word.entry, TRUE);
    sp = es->base + es->sp - sizeof(Stacel);
    type = stacel_types[STACEL(sp)->type];
    operation_with_check_cond(type, to_boolean, condition, ss);
  }
}

void _builtin_if(SikleState* ss)
{
  SAnonymousWord word0;
  SAnonymousWord word1;
  int condition = 0;
  
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  type_check(get_stacel_type(ss), STACEL_ANONYMOUS_WORD);
  type.operation->pop(ss, STACEL(&word0));

  type_check(get_stacel_type(ss), STACEL_ANONYMOUS_WORD);
  type.operation->pop(ss, STACEL(&word1));

  sp = es->base + es->sp - sizeof(Stacel);
  type = stacel_types[STACEL(sp)->type];
  
  operation_with_check_cond(type, to_boolean, condition, ss);

  if(condition) {
    exec_gramel(ss, word1.entry, TRUE);
  }
  else {
    exec_gramel(ss, word0.entry, TRUE);
  }
}

void _builtin_p(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, p, ss);
}

void _builtin_dup(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, dup, ss);
}

void _builtin_drop(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  void* sp = es->base + es->sp - sizeof(Stacel);

  StacelType type = stacel_types[STACEL(sp)->type];

  operation_with_check(type, drop, ss);
}

void _builtin_pull(SikleState* ss)
{
  ExecStack* es = ss->exec_stack;
  unsigned int sp;
  int i = 0;

  StacelType type;
  SInteger integer;
  SContainerStruct cont;

  type_check(get_stacel_type(ss), STACEL_INTEGER);
  type = stacel_types[STACEL_INTEGER];
  type.operation->pop(ss, STACEL(&integer));

  /* dirty hack */
  sp = es->sp;

  while(i < integer.value) {
    type = stacel_types[get_stacel_type(ss)];
    type.operation->drop(ss);
    i++;
  }

  type = stacel_types[get_stacel_type(ss)];
  type.operation->pop(ss, STACEL(&cont));
  
  es->sp = sp;
  type.operation->push(ss, STACEL(&cont));
}

void _builtin_to_s(SikleState* ss)
{
  StacelType type = stacel_types[get_stacel_type(ss)];
  SString sstring;
  SPString string;

  if(type.operation->to_string != NULL) {
    string = type.operation->to_string(ss);
  }
  else {
    printf("ERROR: 'to_string' not support for %s\n", type.name);
    exit(0);
  }

  sstring.type = STACEL_STRING;
  sstring.value = string;

  type = stacel_types[STACEL_STRING];
  type.operation->push(ss, STACEL(&sstring));
}

void register_builtin_type_words(SikleState* ss)
{
  int i;
  for(i = 0; i < STACEL_TYPE_END; i++) {
    StacelType type = stacel_types[i];
    if(type.operation->register_builtin_words != NULL)
      type.operation->register_builtin_words(ss);
  }
}

void register_builtin_words(SikleState* ss)
{
  register_builtin_word(ss, "+", _builtin_add);
  register_builtin_word(ss, "-", _builtin_sub);
  register_builtin_word(ss, "*", _builtin_mul);
  register_builtin_word(ss, "/", _builtin_div);
  register_builtin_word(ss, ">", _builtin_gt);
  register_builtin_word(ss, ">=", _builtin_nlt);
  register_builtin_word(ss, "<", _builtin_lt);
  register_builtin_word(ss, "<=", _builtin_ngt);
  register_builtin_word(ss, "==", _builtin_equ);
  register_builtin_word(ss, "if", _builtin_if);
  register_builtin_word(ss, "repeat", _builtin_repeat);
  register_builtin_word(ss, "dup", _builtin_dup);
  register_builtin_word(ss, "drop", _builtin_drop);
  register_builtin_word(ss, "p", _builtin_p);
  register_builtin_word(ss, "swp", _builtin_swp);
  register_builtin_word(ss, "pull", _builtin_pull);
  register_builtin_word(ss, "to_s", _builtin_to_s);

  register_builtin_type_words(ss);
}

